/**
 * Created by glyn on 2024/3/15.
 */
#pragma once

#if defined(__linux__) || defined(__APPLE__)
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/un.h>
#include <netinet/in.h>
#elif _WIN32
#include <winsock32.h>
#endif
#include <string>
#include <functional>
#include <cerrno>
#include <string.h>

#define FDR_UNUSED(expr){ (void)(expr); } 
#define FDR_ON_ERROR std::function<void(int, std::string)> onError = [](int errorCode, std::string errorMessage){FDR_UNUSED(errorCode); FDR_UNUSED(errorMessage)}

#ifndef AS_DOMAIN_BUFFER_SIZE
#define AS_DOMAIN_BUFFER_SIZE 0x1000 /*4096 bytes*/
#endif

class BaseDomainSocket
{
public:
    enum SocketType
    {
        TCP = SOCK_STREAM,
        UDP = SOCK_DGRAM
    };

    enum SocketCMC
    {
        NET = AF_INET,
        NET6 = AF_INET6,
        LOCALM = AF_LOCAL,
        LOCALS = AF_UNIX

    };

    struct sockaddr_un address;

    void Close() {
        shutdown(this->sock, SHUT_RDWR);
        close(this->sock);
    }

    std::string remotePath() const { return pathToString(this->address); }

    int fileDescriptor() const { return this->sock; }


    // Bind the address(0.0.0.0) & port of the server.
    void Bind(uint16_t id, FDR_ON_ERROR) { this->Bind(""+id, onError); }

    void Bind(std::string str, FDR_ON_ERROR) { this->Bind(str.c_str(), onError); }

    // Bind the custom address & port of the server.
    void Bind(const char* path, FDR_ON_ERROR)
    {

        this->address.sun_family = LOCALM;

        if(access(path, F_OK) == 0)
        {
            unlink(path);
        }

        strcpy(this->address.sun_path, path);


        if (bind(this->sock, (const sockaddr *)&this->address, sizeof(this->address)) == -1)
        {
            onError(errno, "Cannot bind the socket.");
            return;
        }
    }


protected:
    int sock = 0;

    // Get std::string value of the IP from a `sockaddr_un` address struct

    static std::string pathToString(const sockaddr_un& addr)
    {
        return std::string(addr.sun_path);
    }


    BaseDomainSocket(FDR_ON_ERROR, SocketType sockType = TCP,SocketCMC socketCmc = NET , int socketId = -1)
    {
        if (socketId == -1)
        {
            this->sock = socket(socketCmc, sockType, 0);
            
            if ( this->sock == -1 )
            {
                onError(errno, "Socket creating error.");
            }
        }
        else
        {
            this->sock = socketId;
        }
    }
    virtual ~BaseDomainSocket(){}
};